package cn.yiya.shiji.views;

import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.Log;
import android.widget.ImageView.ScaleType;

import java.util.HashSet;
import java.util.Set;

@SuppressLint("Override") public class RoundedDrawable extends Drawable {

	 public static final String TAG = "RoundedDrawable";
	  public static final int DEFAULT_BORDER_COLOR = Color.BLACK;

	  public static final int CORNER_TOP_LEFT = 0;
	  public static final int CORNER_TOP_RIGHT = 1;
	  public static final int CORNER_BOTTOM_RIGHT = 2;
	  public static final int CORNER_BOTTOM_LEFT = 3;

	  private final RectF mBounds = new RectF();
	  private final RectF mDrawableRect = new RectF();
	  private final RectF mBitmapRect = new RectF();
	  private final Bitmap mBitmap;
	  private final Paint mBitmapPaint;
	  private final int mBitmapWidth;
	  private final int mBitmapHeight;
	  private final RectF mBorderRect = new RectF();
	  private final Paint mBorderPaint;
	  private final Matrix mShaderMatrix = new Matrix();
	  private final RectF mSquareCornersRect = new RectF();

	  private Shader.TileMode mTileModeX = Shader.TileMode.CLAMP;
	  private Shader.TileMode mTileModeY = Shader.TileMode.CLAMP;
	  private boolean mRebuildShader = true;

	  // [ topLeft, topRight, bottomLeft, bottomRight ]
	  private float mCornerRadius = 0f;
	  private final boolean[] mCornersRounded = new boolean[] { true, true, true, true };

	  private boolean mOval = false;
	  private float mBorderWidth = 0;
	  private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
	  private ScaleType mScaleType = ScaleType.FIT_CENTER;

	  public RoundedDrawable(Bitmap bitmap) {
	    mBitmap = bitmap;

	    mBitmapWidth = bitmap.getWidth();
	    mBitmapHeight = bitmap.getHeight();
	    mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);

	    mBitmapPaint = new Paint();
	    mBitmapPaint.setStyle(Paint.Style.FILL);
	    mBitmapPaint.setAntiAlias(true);

	    mBorderPaint = new Paint();
	    mBorderPaint.setStyle(Paint.Style.STROKE);
	    mBorderPaint.setAntiAlias(true);
	    mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
	    mBorderPaint.setStrokeWidth(mBorderWidth);
	  }

	  public static RoundedDrawable fromBitmap(Bitmap bitmap) {
	    if (bitmap != null) {
	      return new RoundedDrawable(bitmap);
	    } else {
	      return null;
	    }
	  }

	  public static Drawable fromDrawable(Drawable drawable) {
	    if (drawable != null) {
	      if (drawable instanceof RoundedDrawable) {
	        // just return if it's already a RoundedDrawable
	        return drawable;
	      } else if (drawable instanceof LayerDrawable) {
	        LayerDrawable ld = (LayerDrawable) drawable;
	        int num = ld.getNumberOfLayers();

	        // loop through layers to and change to RoundedDrawables if possible
	        for (int i = 0; i < num; i++) {
	          Drawable d = ld.getDrawable(i);
	          ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d));
	        }
	        return ld;
	      }

	      // try to get a bitmap from the drawable and
	      Bitmap bm = drawableToBitmap(drawable);
	      if (bm != null) {
	        return new RoundedDrawable(bm);
	      }
	    }
	    return drawable;
	  }

	  public static Bitmap drawableToBitmap(Drawable drawable) {
	    if (drawable instanceof BitmapDrawable) {
	      return ((BitmapDrawable) drawable).getBitmap();
	    }

	    Bitmap bitmap;
	    int width = Math.max(drawable.getIntrinsicWidth(), 2);
	    int height = Math.max(drawable.getIntrinsicHeight(), 2);
	    try {
	      bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
	      Canvas canvas = new Canvas(bitmap);
	      drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
	      drawable.draw(canvas);
	    } catch (Exception e) {
	      e.printStackTrace();
	      Log.w(TAG, "Failed to create bitmap from drawable!");
	      bitmap = null;
	    }

	    return bitmap;
	  }

	  public Bitmap getSourceBitmap() {
	    return mBitmap;
	  }

	  @Override
	  public boolean isStateful() {
	    return mBorderColor.isStateful();
	  }

	  @Override
	  protected boolean onStateChange(int[] state) {
	    int newColor = mBorderColor.getColorForState(state, 0);
	    if (mBorderPaint.getColor() != newColor) {
	      mBorderPaint.setColor(newColor);
	      return true;
	    } else {
	      return super.onStateChange(state);
	    }
	  }

	  private void updateShaderMatrix() {
	    float scale;
	    float dx;
	    float dy;

	    switch (mScaleType) {
	      case CENTER:
	        mBorderRect.set(mBounds);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);

	        mShaderMatrix.reset();
	        mShaderMatrix.setTranslate((int) ((mBorderRect.width() - mBitmapWidth) * 0.5f + 0.5f),
	            (int) ((mBorderRect.height() - mBitmapHeight) * 0.5f + 0.5f));
	        break;

	      case CENTER_CROP:
	        mBorderRect.set(mBounds);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);

	        mShaderMatrix.reset();

	        dx = 0;
	        dy = 0;

	        if (mBitmapWidth * mBorderRect.height() > mBorderRect.width() * mBitmapHeight) {
	          scale = mBorderRect.height() / (float) mBitmapHeight;
	          dx = (mBorderRect.width() - mBitmapWidth * scale) * 0.5f;
	        } else {
	          scale = mBorderRect.width() / (float) mBitmapWidth;
	          dy = (mBorderRect.height() - mBitmapHeight * scale) * 0.5f;
	        }

	        mShaderMatrix.setScale(scale, scale);
	        mShaderMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
	        break;

	      case CENTER_INSIDE:
	        mShaderMatrix.reset();

	        if (mBitmapWidth <= mBounds.width() && mBitmapHeight <= mBounds.height()) {
	          scale = 1.0f;
	        } else {
	          scale = Math.min(mBounds.width() / (float) mBitmapWidth,
	              mBounds.height() / (float) mBitmapHeight);
	        }

	        dx = (int) ((mBounds.width() - mBitmapWidth * scale) * 0.5f + 0.5f);
	        dy = (int) ((mBounds.height() - mBitmapHeight * scale) * 0.5f + 0.5f);

	        mShaderMatrix.setScale(scale, scale);
	        mShaderMatrix.postTranslate(dx, dy);

	        mBorderRect.set(mBitmapRect);
	        mShaderMatrix.mapRect(mBorderRect);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
	        break;

	      default:
	      case FIT_CENTER:
	        mBorderRect.set(mBitmapRect);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.CENTER);
	        mShaderMatrix.mapRect(mBorderRect);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
	        break;

	      case FIT_END:
	        mBorderRect.set(mBitmapRect);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.END);
	        mShaderMatrix.mapRect(mBorderRect);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
	        break;

	      case FIT_START:
	        mBorderRect.set(mBitmapRect);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.START);
	        mShaderMatrix.mapRect(mBorderRect);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
	        mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
	        break;

	      case FIT_XY:
	        mBorderRect.set(mBounds);
	        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
	        mShaderMatrix.reset();
	        mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
	        break;
	    }

	    mDrawableRect.set(mBorderRect);
	  }

	  @Override
	  protected void onBoundsChange(Rect bounds) {
	    super.onBoundsChange(bounds);

	    mBounds.set(bounds);

	    updateShaderMatrix();
	  }

	  @Override
	  public void draw(Canvas canvas) {
	    if (mRebuildShader) {
	      BitmapShader bitmapShader = new BitmapShader(mBitmap, mTileModeX, mTileModeY);
	      if (mTileModeX == Shader.TileMode.CLAMP && mTileModeY == Shader.TileMode.CLAMP) {
	        bitmapShader.setLocalMatrix(mShaderMatrix);
	      }
	      mBitmapPaint.setShader(bitmapShader);
	      mRebuildShader = false;
	    }

	    if (mOval) {
	      if (mBorderWidth > 0) {
	        canvas.drawOval(mDrawableRect, mBitmapPaint);
	        canvas.drawOval(mBorderRect, mBorderPaint);
	      } else {
	        canvas.drawOval(mDrawableRect, mBitmapPaint);
	      }
	    } else {
	      if (any(mCornersRounded)) {
	        float radius = mCornerRadius;
	        if (mBorderWidth > 0) {
	          canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
	          canvas.drawRoundRect(mBorderRect, radius, radius, mBorderPaint);
	          redrawBitmapForSquareCorners(canvas);
	          redrawBorderForSquareCorners(canvas);
	        } else {
	          canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
	          redrawBitmapForSquareCorners(canvas);
	        }
	      } else {
	        canvas.drawRect(mDrawableRect, mBitmapPaint);
	        canvas.drawRect(mBorderRect, mBorderPaint);
	      }
	    }
	  }

	  private void redrawBitmapForSquareCorners(Canvas canvas) {
	    if (all(mCornersRounded)) {
	      // no square corners
	      return;
	    }

	    if (mCornerRadius == 0) {
	      return; // no round corners
	    }

	    float left = mDrawableRect.left;
	    float top = mDrawableRect.top;
	    float right = left + mDrawableRect.width();
	    float bottom = top + mDrawableRect.height();
	    float radius = mCornerRadius;

	    if (!mCornersRounded[CORNER_TOP_LEFT]) {
	      mSquareCornersRect.set(left, top, left + radius, top + radius);
	      canvas.drawRect(mSquareCornersRect, mBitmapPaint);
	    }

	    if (!mCornersRounded[CORNER_TOP_RIGHT]) {
	      mSquareCornersRect.set(right - radius, top, right, radius);
	      canvas.drawRect(mSquareCornersRect, mBitmapPaint);
	    }

	    if (!mCornersRounded[CORNER_BOTTOM_RIGHT]) {
	      mSquareCornersRect.set(right - radius, bottom - radius, right, bottom);
	      canvas.drawRect(mSquareCornersRect, mBitmapPaint);
	    }

	    if (!mCornersRounded[CORNER_BOTTOM_LEFT]) {
	      mSquareCornersRect.set(left, bottom - radius, left + radius, bottom);
	      canvas.drawRect(mSquareCornersRect, mBitmapPaint);
	    }
	  }

	  private void redrawBorderForSquareCorners(Canvas canvas) {
	    if (all(mCornersRounded)) {
	      // no square corners
	      return;
	    }

	    if (mCornerRadius == 0) {
	      return; // no round corners
	    }

	    float left = mDrawableRect.left;
	    float top = mDrawableRect.top;
	    float right = left + mDrawableRect.width();
	    float bottom = top + mDrawableRect.height();
	    float radius = mCornerRadius;
	    float offset = mBorderWidth / 2;

	    if (!mCornersRounded[CORNER_TOP_LEFT]) {
	      canvas.drawLine(left - offset, top, left + radius, top, mBorderPaint);
	      canvas.drawLine(left, top - offset, left, top + radius, mBorderPaint);
	    }

	    if (!mCornersRounded[CORNER_TOP_RIGHT]) {
	      canvas.drawLine(right - radius - offset, top, right, top, mBorderPaint);
	      canvas.drawLine(right, top - offset, right, top + radius, mBorderPaint);
	    }

	    if (!mCornersRounded[CORNER_BOTTOM_RIGHT]) {
	      canvas.drawLine(right - radius - offset, bottom, right + offset, bottom, mBorderPaint);
	      canvas.drawLine(right, bottom - radius, right, bottom, mBorderPaint);
	    }

	    if (!mCornersRounded[CORNER_BOTTOM_LEFT]) {
	      canvas.drawLine(left - offset, bottom, left + radius, bottom, mBorderPaint);
	      canvas.drawLine(left, bottom - radius, left, bottom, mBorderPaint);
	    }
	  }

	  @Override
	  public int getOpacity() {
	    return PixelFormat.TRANSLUCENT;
	  }

	  public int getAlpha() {
	    return mBitmapPaint.getAlpha();
	  }

	  @Override
	  public void setAlpha(int alpha) {
	    mBitmapPaint.setAlpha(alpha);
	    invalidateSelf();
	  }

	  public ColorFilter getColorFilter() {
	    return mBitmapPaint.getColorFilter();
	  }

	  @Override
	  public void setColorFilter(ColorFilter cf) {
	    mBitmapPaint.setColorFilter(cf);
	    invalidateSelf();
	  }

	  @Override
	  public void setDither(boolean dither) {
	    mBitmapPaint.setDither(dither);
	    invalidateSelf();
	  }

	  @Override
	  public void setFilterBitmap(boolean filter) {
	    mBitmapPaint.setFilterBitmap(filter);
	    invalidateSelf();
	  }

	  @Override
	  public int getIntrinsicWidth() {
	    return mBitmapWidth;
	  }

	  @Override
	  public int getIntrinsicHeight() {
	    return mBitmapHeight;
	  }

	  /**
	   * @return the corner radius.
	   */
	  public float getCornerRadius() {
	    return mCornerRadius;
	  }

	  /**
	   * @param corner the specific corner to get radius of.
	   * @return the corner radius of the specified corner.
	   */
	  public float getCornerRadius(int corner) {
	    return mCornersRounded[corner] ? mCornerRadius : 0f;
	  }

	  /**
	   * Sets all corners to the specified radius.
	   *
	   * @param radius the radius.
	   * @return the {@link RoundedDrawable} for chaining.
	   */
	  public RoundedDrawable setCornerRadius(float radius) {
	    setCornerRadius(radius, radius, radius, radius);
	    return this;
	  }

	  /**
	   * Sets the corner radius of one specific corner.
	   *
	   * @param corner the corner.
	   * @param radius the radius.
	   * @return the {@link RoundedDrawable} for chaining.
	   */
	  public RoundedDrawable setCornerRadius(int corner, float radius) {
	    if (radius != 0 && mCornerRadius != 0 && mCornerRadius != radius) {
	      throw new IllegalArgumentException("Multiple nonzero corner radii not yet supported.");
	    }

	    if (radius == 0) {
	      if (only(corner, mCornersRounded)) {
	        mCornerRadius = 0;
	      }
	      mCornersRounded[corner] = false;
	    } else {
	      if (mCornerRadius == 0) {
	        mCornerRadius = radius;
	      }
	      mCornersRounded[corner] = true;
	    }

	    return this;
	  }

	  /**
	   * Sets the corner radii of all the corners.
	   *
	   * @param topLeft top left corner radius.
	   * @param topRight top right corner radius
	   * @param bottomRight bototm right corner radius.
	   * @param bottomLeft bottom left corner radius.
	   * @return the {@link RoundedDrawable} for chaining.
	   */
	  public RoundedDrawable setCornerRadius(float topLeft, float topRight, float bottomRight,
	      float bottomLeft) {
	    Set<Float> radiusSet = new HashSet<Float>(4);
	    radiusSet.add(topLeft);
	    radiusSet.add(topRight);
	    radiusSet.add(bottomRight);
	    radiusSet.add(bottomLeft);

	    radiusSet.remove(0f);

	    if (radiusSet.size() > 1) {
	      throw new IllegalArgumentException("Multiple nonzero corner radii not yet supported.");
	    }

	    if (!radiusSet.isEmpty()) {
	      float radius = radiusSet.iterator().next();
	      if (Float.isInfinite(radius) || Float.isNaN(radius) || radius < 0) {
	        throw new IllegalArgumentException("Invalid radius value: " + radius);
	      }
	      mCornerRadius = radius;
	    } else {
	      mCornerRadius = 0f;
	    }

	    mCornersRounded[CORNER_TOP_LEFT] = topLeft > 0;
	    mCornersRounded[CORNER_TOP_RIGHT] = topRight > 0;
	    mCornersRounded[CORNER_BOTTOM_RIGHT] = bottomRight > 0;
	    mCornersRounded[CORNER_BOTTOM_LEFT] = bottomLeft > 0;
	    return this;
	  }

	  public float getBorderWidth() {
	    return mBorderWidth;
	  }

	  public RoundedDrawable setBorderWidth(float width) {
	    mBorderWidth = width;
	    mBorderPaint.setStrokeWidth(mBorderWidth);
	    return this;
	  }

	  public int getBorderColor() {
	    return mBorderColor.getDefaultColor();
	  }

	  public RoundedDrawable setBorderColor(int color) {
	    return setBorderColor(ColorStateList.valueOf(color));
	  }

	  public ColorStateList getBorderColors() {
	    return mBorderColor;
	  }

	  public RoundedDrawable setBorderColor(ColorStateList colors) {
	    mBorderColor = colors != null ? colors : ColorStateList.valueOf(0);
	    mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
	    return this;
	  }

	  public boolean isOval() {
	    return mOval;
	  }

	  public RoundedDrawable setOval(boolean oval) {
	    mOval = oval;
	    return this;
	  }

	  public ScaleType getScaleType() {
	    return mScaleType;
	  }

	  public RoundedDrawable setScaleType(ScaleType scaleType) {
	    if (scaleType == null) {
	      scaleType = ScaleType.FIT_CENTER;
	    }
	    if (mScaleType != scaleType) {
	      mScaleType = scaleType;
	      updateShaderMatrix();
	    }
	    return this;
	  }

	  public Shader.TileMode getTileModeX() {
	    return mTileModeX;
	  }

	  public RoundedDrawable setTileModeX(Shader.TileMode tileModeX) {
	    if (mTileModeX != tileModeX) {
	      mTileModeX = tileModeX;
	      mRebuildShader = true;
	      invalidateSelf();
	    }
	    return this;
	  }

	  public Shader.TileMode getTileModeY() {
	    return mTileModeY;
	  }

	  public RoundedDrawable setTileModeY(Shader.TileMode tileModeY) {
	    if (mTileModeY != tileModeY) {
	      mTileModeY = tileModeY;
	      mRebuildShader = true;
	      invalidateSelf();
	    }
	    return this;
	  }

	  private static boolean only(int index, boolean[] booleans) {
	    for (int i = 0, len = booleans.length; i < len; i++) {
	      if (booleans[i] != (i == index)) {
	        return false;
	      }
	    }
	    return true;
	  }

	  private static boolean any(boolean[] booleans) {
	    for (boolean b : booleans) {
	      if (b) { return true; }
	    }
	    return false;
	  }

	  private static boolean all(boolean[] booleans) {
	    for (boolean b : booleans) {
	      if (b) { return false; }
	    }
	    return true;
	  }

	  public Bitmap toBitmap() {
	    return drawableToBitmap(this);
	  }
}
